package com.rustic.server.service.impl;

import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.xy.base.server.config.AppConfig;
import cn.xy.base.server.utils.PageUtil;
import cn.xy.commons.cache.CacheManager;
import cn.xy.commons.enums.DataStatus;
import cn.xy.commons.enums.YesNoStatus;
import cn.xy.commons.exception.JzRuntimeException;
import cn.xy.commons.util.JwtUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rustic.server.dao.UserDao;
import com.rustic.server.dto.user.UpdatePersonalDTO;
import com.rustic.server.dto.user.UserUpdateDTO;
import com.rustic.server.po.User;
import com.rustic.server.query.UserAdminListQuery;
import com.rustic.server.service.UserService;
import com.rustic.server.service.UserWeixinService;
import com.rustic.server.vo.user.UserListVO;
import com.rustic.server.vo.user.UserSimpleVO;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * User对应的服务类实现
 *
 * @author auto
 */

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private UserWeixinService userWeixinService;

    @Override
    public List<User> listByIdIn(List<Integer> idList) {
        if (CollUtil.isEmpty(idList)) {
            return new ArrayList<>();
        }
        return this.listByIds(idList);
    }

    @Override
    public Map<Integer, User> mapByIdIn(List<Integer> idList) {
        List<User> userList = this.listByIdIn(idList);
        return userList.stream().collect(Collectors.toMap(User::getId, b -> b));
    }

    @Override
    public String login(String ip, User user) {
        // 生成jwt
        String token = this.createJwt(user);

        // token存入缓存中
        String key = UserService.LOGIN_PREFIX + user.getId();
        LinkedList<String> tokenList = cacheManager.get(key);
        if (tokenList == null) {
            tokenList = new LinkedList<>();
        }
        if (tokenList.size() >= UserService.MAX_LOCATION) {
            tokenList.poll();
        }
        tokenList.add(token);
        cacheManager.set(key, tokenList, UserService.MAX_LIFE);

        this.updateLastLoginInfo(user.getId(), ip);
        return token;
    }

    @Override
    public String createJwt(User user) {
        if (user == null) {
            return null;
        }
        Map<String, Object> claims = new HashMap<>(1);
        claims.put(UserService.USER_ID, user.getId());
        return JwtUtil.createJwt(claims, appConfig.getJwtSubject(), appConfig.getJwtSecret(), appConfig.getJwtLife());
    }

    @Override
    public void updateLastLoginInfo(Integer id, String ip) {
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(User::getId, id);
        updateWrapper.set(User::getLastIp, ip);
        updateWrapper.set(User::getLastTime, DateUtil.date());
        this.update(updateWrapper);
    }

    @Override
    public UserSimpleVO getSimpleById(Integer id) {
        User user = this.getById(id);
        if (user == null || YesNoStatus.isYes(user.getIsLock())) {
            return null;
        }
        UserSimpleVO userSimpleVO = new UserSimpleVO(user);
        String mobile = CharSequenceUtil.isEmpty(user.getMobile()) ? "-" : CharSequenceUtil.replace(user.getMobile(), 3, 7, '*');
        userSimpleVO.setMobile(mobile);
        // TODO 填写用户基本信息
        return userSimpleVO;
    }

    @Override
    public User createByWxMaUserInfo(String appId, WxMaUserInfo userInfo, String openId, String ip) {
        User user = new User();
        user.setNickname(userInfo.getNickName());
        user.setAvatar(userInfo.getAvatarUrl());
        user.setIsLock(YesNoStatus.NO.getValue());
        user.setDataStatus(DataStatus.NORMAL.getValue());
        user.setCreateIp(ip);
        this.save(user);

        userWeixinService.tryAdd(user.getId(), appId, userInfo, openId);
        return user;
    }

    @Override
    public User getByMatch(String account) {
        if (Validator.isEmail(account)) {
            return this.getByEmail(account);
        } else {
            return this.getByMobile(account);
        }
    }

    @Override
    public User getByMobile(String mobile) {
        if (CharSequenceUtil.isEmpty(mobile)) {
            return null;
        }
        return this.getOne(new LambdaQueryWrapper<User>().eq(User::getMobile, mobile));
    }

    @Override
    public User getByNickname(String nickname) {
        if (CharSequenceUtil.isEmpty(nickname)) {
            return null;
        }
        return this.getOne(new LambdaQueryWrapper<User>().eq(User::getNickname, nickname));
    }

    @Override
    public void updateMobile(int userId, String mobile, YesNoStatus verified) {
        this.update(new LambdaUpdateWrapper<User>()
                .eq(User::getId, userId)
                .set(User::getMobile, mobile)
        );
    }

    @Override
    public User createByWxOauth2UserInfo(String appId, WxOAuth2UserInfo wxOauth2UserInfo, String ip) {
        User user = new User();
        user.setNickname(wxOauth2UserInfo.getNickname());
        user.setAvatar(wxOauth2UserInfo.getHeadImgUrl());
        user.setIsLock(YesNoStatus.NO.getValue());
        user.setDataStatus(DataStatus.NORMAL.getValue());
        user.setCreateIp(ip);
        this.save(user);

        userWeixinService.tryAdd(user.getId(), appId, wxOauth2UserInfo);
        return user;
    }

    @Override
    public void logout(int userId) {
        String key = LOGIN_PREFIX + userId;
        cacheManager.del(key);
    }

    @Override
    public String encodePassword(String password, String salt) {
        return SecureUtil.md5(password + salt + password);
    }

    @Override
    public void updatePassword(Integer id, String password) {
        String salt = RandomUtil.randomString(32);
        String newPassword = this.encodePassword(password, salt);
        this.update(new LambdaUpdateWrapper<User>().eq(User::getId, id).set(User::getPassword, newPassword).set(User::getSalt, salt));
    }

    @Override
    public void bindMobile(int userId, String mobile) {
        User user = this.getByMobile(mobile);
        if (user != null) {
            if (user.getId() == userId) {
                throw new JzRuntimeException("该手机号已于你的账号绑定");
            } else {
                throw new JzRuntimeException("该手机号已于其他账号绑定");
            }
        }
        this.updateMobile(userId, mobile, YesNoStatus.YES);
    }

    @Override
    public boolean isLogin(int userId, String token) {
        String key = LOGIN_PREFIX + userId;
        LinkedList<String> tokenList = cacheManager.get(key);
        if (tokenList != null) {
            return tokenList.contains(token);
        }
        return false;
    }

    @Override
    public void renewLogin(int userId) {
        String key = LOGIN_PREFIX + userId;
        cacheManager.expire(key, MAX_LIFE);
    }

    @Override
    public User addUserByAccount(String account, String password, String ip, Integer nationalityId) {
        RLock lock = redissonClient.getLock("add_user_" + account);
        try {
            lock.lock(10, TimeUnit.SECONDS);
            User user = this.getByMatch(account);
            if (user != null) {
                throw new JzRuntimeException("该账号已被注册，请直接登陆");
            }
            boolean isEmail = Validator.isEmail(account);
            user = new User();
            if (isEmail) {
                user.setEmail(account);
                ;
            } else {
                user.setMobile(account);
            }
            String salt = RandomUtil.randomString(32);
            String newPassword = this.encodePassword(password, salt);
            user.setNickname(account);
            user.setSalt(salt);
            user.setPassword(newPassword);
            user.setCreateIp(ip);
            user.setDataStatus(DataStatus.NORMAL.getValue());
            this.save(user);
            lock.unlock();
            return user;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public Page<UserListVO> getPageList(UserAdminListQuery userQuery) {
        // fixme 用户的分页查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(CharSequenceUtil.isNotEmpty(userQuery.getNickname()), User::getNickname, userQuery.getNickname());
        wrapper.like(userQuery.getId() != null && userQuery.getId() != 0, User::getId, userQuery.getId());
        wrapper.eq(userQuery.getSex() != null && userQuery.getSex() != 0, User::getSex, userQuery.getSex());
        wrapper.like(CharSequenceUtil.isNotEmpty(userQuery.getMobile()), User::getMobile, userQuery.getMobile());
        wrapper.like(CharSequenceUtil.isNotEmpty(userQuery.getEmail()), User::getEmail, userQuery.getEmail());
        wrapper.eq(CharSequenceUtil.isNotEmpty(userQuery.getIsLock()), User::getIsLock, userQuery.getIsLock());
        Page<User> pageData = this.page(userQuery.toPage(), wrapper);
        List<User> userList = pageData.getRecords();
        if (CollUtil.isEmpty(userList)) {
            return new Page<>();
        }
        // 组装数据
        List<UserListVO> userListVOS = userList.stream().map(UserListVO::new).collect(Collectors.toList());
        return PageUtil.toPage(pageData, userListVOS);
    }

    @Override
    public void updateUser(UserUpdateDTO userUpdateDTO) {
        // TODO 用户更新
    }

    @Override
    public void updateStatusBatchById(List<Integer> ids, String status) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        this.update(new LambdaUpdateWrapper<User>()
                .set(User::getIsLock, status)
                .in(User::getId, ids));
        if (YesNoStatus.isYes(status)) {
            for (int id : ids) {
                this.logout(id);
            }
        }
    }


    @Override
    public User getByEmail(String email) {
        if (CharSequenceUtil.isEmpty(email)) {
            return null;
        }
        return this.getOne(new LambdaQueryWrapper<User>().eq(User::getEmail, email));
    }

    @Override
    public void checkMobileAndEmail(String mobile, String email, int userId) {
        if (CharSequenceUtil.isNotEmpty(mobile)) {
            User userByMobile = this.getByMobile(mobile);
            if (userByMobile != null && userByMobile.getId() != userId) {
                throw new JzRuntimeException("该手机号已有其他会员绑定");
            }
        }
        if (CharSequenceUtil.isNotEmpty(email)) {
            User userByEmail = this.getByEmail(email);
            if (userByEmail != null && userByEmail.getId() != userId) {
                throw new JzRuntimeException("该邮箱已有其他会员绑定");
            }
        }
    }

    @Override
    public void updateUserToWeb(UpdatePersonalDTO updatePersonalDTO, int userId) {
        // 判断昵称是否有重复
        User selectUser = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getNickname, updatePersonalDTO.getNickname()));
        if (selectUser != null) {
            if (selectUser.getId() != userId) {
                throw new JzRuntimeException("昵称已被占用，请重新填写!");
            }
        }
        User user = updatePersonalDTO.toUser();
        user.setId(userId);
        this.updateById(user);
    }

}
